package com.quectel.util.aspect.lock;

import cn.hutool.core.util.ReflectUtil;
import com.google.common.base.Preconditions;
import com.quectel.util.aspect.lock.annotation.Lock;
import com.quectel.util.client.redis.RedisClient;
import com.quectel.util.common.SpringExpressionParser;
import com.quectel.util.ex.aspect.lock.ServiceLockException;
import com.quectel.util.redis.RedisUtils;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.collections4.CollectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 拦截Lock注解修饰的方法
 *
 * @author caolu
 * @email louis.cao@quectel.com
 * @date 2023-11-28 14:06:00
 */
public class DistributedLockMethodInterceptor implements MethodInterceptor {

    /**
     * 获取方法参数列表中参数真正的名称
     */
    private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    private RedisClient redisClient;

    public DistributedLockMethodInterceptor(RedisClient redisClient) {
        this.redisClient = redisClient;
    }

    @Nullable
    @Override
    public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {

        List<String> lockKeys = getLockKeys(invocation);
        if (CollectionUtils.isEmpty(lockKeys)) {
            // 不需要锁直接放行即可
            return invocation.proceed();
        } else {
            java.util.concurrent.locks.Lock lock = lockKeys.size() == 1
                    ?
                    RedisUtils.getSpinLock(lockKeys.get(0))
                    :
                    RedisUtils.getMultiSpinLock(lockKeys.toArray(new String[]{}));
            if (TransactionSynchronizationManager.isActualTransactionActive()) {
                lock.lock();
                try {
                    // 如果开启事物事务则需要事务完成后在进行锁释放
                    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                        @Override
                        public void afterCompletion(int status) {
                            // 解锁逻辑需要等到事务执行完成
                            lock.unlock();
                        }
                    });
                } catch (Exception e) {
                    // 注册事务结束通知事件失败 直接释放锁
                    lock.unlock();
                    throw new ServiceLockException("The method(" + invocation.getMethod() + ") register synchronization error", e);
                }
                return invocation.proceed();
            } else {
                // 没有事务直接加锁
                lock.lock();
                try {
                    return invocation.proceed();
                } catch (Exception e) {
                    // 加锁失败  抛出异常给上层
                    throw new ServiceLockException("The method(" + invocation.getMethod() + ") execute in lock failed ", e);
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 获取锁定标识
     *
     * @param invocation 方法句柄
     * @return
     */
    private List<String> getLockKeys(MethodInvocation invocation) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Method method = invocation.getMethod();
        Lock lock = method.getAnnotation(Lock.class);

        String methodDetail = method.toString();

        // 判断锁表达式或者抽取方法是否存在
        Preconditions.checkArgument(
                lock.lockExpresses() != null && lock.lockExpresses().length > 0
                        ||
                        !Objects.equals(Lock.LockKeysExtractor.class, lock.extraLockKeysClass())
                ,
                String.format(
                        "The lock method(%s) at least choose one from extra express and extra class, execute abort"
                        , methodDetail
                )
        );

        List<String> lockKeys = new ArrayList<>();
        if (lock.lockExpresses() != null && lock.lockExpresses().length > 0) {
            // 优先按照按照表达式计算
            HashMap<String, Object> elParams = new HashMap<>();

            for (String express : lock.lockExpresses()) {
                if (express != null && express.contains("null")) {
                    //还没有解析前包含null字符串是不被允许的
                    throw new ServiceLockException("The lock express shouldn't contains null strings before parsing");
                }
                if (express != null && express.contains("#{#")) {
                    // 可能需要正则解析
                    Object[] args = invocation.getArguments();
                    String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
                    if (args != null && parameterNames != null) {
                        for (int i = 0; i < parameterNames.length; i++) {
                            elParams.putIfAbsent(parameterNames[i], args[i]);
                        }
                    }
                    String lockKey = SpringExpressionParser.getValue(elParams, express, false, String.class);
                    lockKeys.add(lockKey);
                } else {
                    // 不需要正则
                    lockKeys.add(express);
                }
            }
        } else {
            // 按照抽取类
            Lock.LockKeysExtractor lockKeysExtractor = lock.extraLockKeysClass().getDeclaredConstructor().newInstance();
            String[] extraKeys = ReflectUtil.invoke(lockKeysExtractor, "extraLockKeys", new Object[]{invocation.getArguments()});
            if (extraKeys == null || extraKeys.length == 0) {
                // 没有提取到内容
                throw new ServiceLockException(
                        String.format(
                                "The lock method(%s) extra class not return lock keys ,execute abort"
                                , methodDetail
                        )
                );
            }
            Collections.addAll(lockKeys, extraKeys);
        }
        // lockKey中包含null字符串的可能是新增方法可以不加锁（极低概率），所以在这里进行过滤
        return lockKeys.stream().filter(Objects::nonNull)
                .filter(v -> !v.contains("null")).collect(Collectors.toList());
    }
}
